#include "CManagerFile.h"

CManagerFile::CManagerFile(DBFrecuencias::MaxFileSize size) {
	this->maxSize = size;
	this->tamanio = TAMANIO_REGISTRO_FIJO;
	this->stream.open(DB_FRECUENCIA, std::ios::binary | std::ios::in| std::ios::out);
	bool dirty=false;
	if (!this->stream.is_open()) {
		this->stream.open(DB_FRECUENCIA, std::ios::binary | std::ios::out| std::ios::trunc);
		this->stream.seekp(0,std::ios::beg);
		this->stream.write(reinterpret_cast<const char*>(&dirty),sizeof(bool));
		this->stream.close();
		this->stream.open(DB_FRECUENCIA, std::ios::binary | std::ios::in| std::ios::out);
	}
	this->stream.seekg(0,std::ios::beg);
	this->stream.read(reinterpret_cast<char*>(&dirty),sizeof(bool));
	this->space = new CManagerSpace(&(this->stream));
	if(!dirty){
		this->cargarIndiceRegistros();
	} else {
		this->reindexar();
	}
	dirty=true;
	this->stream.seekp(0,std::ios::beg);
	this->stream.write(reinterpret_cast<const char*>(&dirty),sizeof(bool));
	this->reorganizar = false;
}

/*
 * Estructura del archivo 
 * [Flag Borrado/Valido | ID Palabra | #Tags | SFrecTag1...| SFrecTagn]
 */

CManagerFile::~CManagerFile() {
	delete(this->space);
	this->guardarIndiceRegistros();
	bool dirty = false;
	this->stream.seekp(0,std::ios::beg);
	this->stream.write(reinterpret_cast<const char*>(&dirty),sizeof(bool));
	this->stream.close();
}

void CManagerFile::eliminarRegistro(DBFrecuencias::Palabra id) {
	DBFrecuencias::IndiceArchivo::iterator it = this->indice.find(id);
	if (it != this->indice.end()) {
		DBFrecuencias::Offset offset = it->second;
		this->stream.seekg(offset, std::ios::beg);
		//this->stream.seekp(offset,std::ios::beg);
		bool borrado = false;
		this->stream.read(reinterpret_cast<char*>(&borrado), sizeof(bool));
		if (!borrado) {
			borrado = true;
			this->stream.seekp(offset, std::ios::beg);
			this->stream.write(reinterpret_cast<const char*>(&borrado),	sizeof(bool));
			DBFrecuencias::CantidadTags cantidad;
			DBFrecuencias::LongitudPalabra longitud;
			this->stream.seekg(offset + sizeof(bool), std::ios::beg);
			this->stream.read(reinterpret_cast<char*>(&longitud),sizeof(DBFrecuencias::LongitudPalabra));
			this->stream.seekg(longitud, std::ios::cur);
			this->stream.read(reinterpret_cast<char*>(&cantidad),sizeof(DBFrecuencias::CantidadTags));
			DBFrecuencias::MaxFileSize size=this->space->agregarEspacio(longitud + (cantidad * sizeof(DBFrecuencias::SElemento)), offset);
			this->indice.erase(it);
			if (size >= this->maxSize) {			
				this->reorganizar = true;
			}
		}
	}
}

void CManagerFile::incrementarFrecuencia(DBFrecuencias::Palabra id,
	DBFrecuencias::TagId tagId, DBFrecuencias::Frecuencia frec) {
	DBFrecuencias::IndiceArchivo::iterator it = this->indice.find(id);
	DBFrecuencias::Offset offset;
	this->managerTag->incrementarNumeroPalabras(tagId,frec);
	if (it != this->indice.end()) {
		offset = it->second;
		this->stream.seekg(offset+sizeof(bool), std::ios::beg);
		//saltea la el string
		DBFrecuencias::LongitudPalabra longitud;
		this->stream.read(reinterpret_cast<char*>(&longitud),sizeof(DBFrecuencias::LongitudPalabra));		
		this->stream.seekg(longitud, std::ios::cur);
		// Estaba en el File
		DBFrecuencias::CantidadTags cantidad;
		this->stream.read(reinterpret_cast<char*>(&cantidad),sizeof(DBFrecuencias::CantidadTags));
		DBFrecuencias::SElemento* elementos = new DBFrecuencias::SElemento[cantidad];
		this->stream.read(reinterpret_cast<char*>(elementos),sizeof(DBFrecuencias::SElemento)*cantidad);
		for (unsigned int i=0; i < cantidad ; i++) {
			if (elementos[i].id == tagId) {
				// Estaba en el File, y estaba el Tag id
				this->stream.seekp(offset + this->tamanio + id.size() +(i* sizeof(DBFrecuencias::SElemento)), std::ios::beg);
				elementos[i].frecuencia += frec;
				this->stream.write(reinterpret_cast<const char*>(&elementos[i]),sizeof(DBFrecuencias::SElemento));
				delete[] elementos;
				return;
			}
		}
		delete[] elementos;
		// Estaba en el File, pero no estaba el Tag id
		this->stream.seekg(offset, std::ios::beg);
		DBFrecuencias::Offset nuevo = this->space->consultarEspacio(id.size()+((cantidad + 1)*(sizeof(DBFrecuencias::SElemento))));
		DBFrecuencias::SElemento elemento;
		elemento.id = tagId;
		elemento.frecuencia = frec;
		this->copiarRegistro(it->second, nuevo);
		this->stream.write(reinterpret_cast<const char*>(&elemento),sizeof(DBFrecuencias::SElemento));
		//Borro el registro
		this->eliminarRegistro(id);
		this->indice.insert(std::pair<DBFrecuencias::Palabra,DBFrecuencias::Offset>(id,nuevo));
		if(this->reorganizar){
			this->reorganizarArchivo();
		}
		//Agrego el registro donde quepa
		this->managerTag->insertarPalabra(tagId,id);
	} else {
		//si no está, consulta en el archivo por un tamana para alocar el registro
		offset=this->space->consultarEspacio(id.size() + sizeof(DBFrecuencias::SElemento));
		//offset=this->space->consultarEspacio(1);
		bool borrado=false;
		DBFrecuencias::CantidadTags cantidad = 1;
		DBFrecuencias::SElemento elemento;
		DBFrecuencias::LongitudPalabra longitud = id.size();
		elemento.id = tagId;
		elemento.frecuencia = frec;
		this->stream.seekp(offset,std::ios::beg);
		this->stream.write(reinterpret_cast<const char*>(&borrado),sizeof(bool));
		this->stream.write(reinterpret_cast<const char*>(&longitud),sizeof(DBFrecuencias::LongitudPalabra));
		this->stream.write(id.c_str(),longitud);		
		this->stream.write(reinterpret_cast<const char*>(&cantidad),sizeof(DBFrecuencias::CantidadTags));
		this->stream.write(reinterpret_cast<const char*>(&elemento),sizeof(DBFrecuencias::SElemento));
		this->indice.insert(std::pair<DBFrecuencias::Palabra,DBFrecuencias::Offset>(id,offset));
		this->managerTag->insertarPalabra(tagId,id);
	}
}

void CManagerFile::consultarFrecuencias(DBFrecuencias::Palabra id,DBFrecuencias::MapFrecuencias& vector) {
	DBFrecuencias::IndiceArchivo::iterator it = this->indice.find(id);
	DBFrecuencias::CantidadTags cantidad;
	if (it != this->indice.end()) {
		DBFrecuencias::Offset offset = it->second;
		this->stream.seekg(offset+sizeof(bool), std::ios::beg);
		DBFrecuencias::LongitudPalabra longitud;
		this->stream.read(reinterpret_cast<char*>(&longitud),sizeof(DBFrecuencias::LongitudPalabra));
		this->stream.seekg(longitud,std::ios::cur);
		this->stream.read(reinterpret_cast<char*>(&cantidad),sizeof(DBFrecuencias::CantidadTags));
		DBFrecuencias::SElemento elemento;
		for (unsigned int i=0; i < cantidad ; i++) {
			this->stream.read(reinterpret_cast<char*>(&elemento),sizeof(DBFrecuencias::SElemento));
			//vector[elemento.id]=elemento.frecuencia;
			DBFrecuencias::MapFrecuencias::iterator iter = vector.find(elemento.id);
			if(iter != vector.end()){
				vector.erase(iter);
			}
			vector.insert(std::pair<DBFrecuencias::TagId,DBFrecuencias::Frecuencia>(elemento.id,elemento.frecuencia));
		}
	}
}

void CManagerFile::copiarRegistro(const DBFrecuencias::Offset origen,const DBFrecuencias::Offset destino) {
	DBFrecuencias::Palabra id;
	DBFrecuencias::SElemento* elemento;
	DBFrecuencias::CantidadTags cantidad;
	DBFrecuencias::LongitudPalabra longitud;
	bool estado;
	this->stream.seekg(origen, std::ios::beg);
	this->stream.read(reinterpret_cast<char*>(&estado), sizeof(bool));
	this->stream.read(reinterpret_cast<char*>(&longitud), sizeof(DBFrecuencias::LongitudPalabra));
	char* buffer = new char[longitud+1];
	
	this->stream.read(reinterpret_cast<char*>(buffer),longitud);
	buffer[longitud]='\0';
	id.assign(buffer);
	
	delete[] buffer;
	
	this->stream.read(reinterpret_cast<char*>(&cantidad),sizeof(DBFrecuencias::CantidadTags));
	elemento = new DBFrecuencias::SElemento[cantidad];
	this->stream.read(reinterpret_cast<char*>(elemento),sizeof(DBFrecuencias::SElemento)*cantidad);
	this->stream.seekp(destino, std::ios::beg);
	this->stream.write(reinterpret_cast<const char*>(&estado), sizeof(bool));
	this->stream.write(reinterpret_cast<const char*>(&longitud), sizeof(DBFrecuencias::LongitudPalabra));
	this->stream.write(id.c_str(),longitud);
	cantidad++;
	this->stream.write(reinterpret_cast<const char*>(&cantidad),sizeof(DBFrecuencias::CantidadTags));
	cantidad--;
	this->stream.write(reinterpret_cast<const char*>(elemento),sizeof(DBFrecuencias::SElemento)*cantidad);
	/*for (unsigned int i=0; i < cantidad -1; i++) {
		DBFrecuencias::SElemento ele = elemento[i];
		this->stream.write(reinterpret_cast<const char*>(&ele),	sizeof(DBFrecuencias::SElemento));
	}*/
	delete[] elemento;

}

void CManagerFile::reindexar() {
	DBFrecuencias::Offset offset;
	DBFrecuencias::Palabra id;
	DBFrecuencias::CantidadTags cantidad;
	DBFrecuencias::LongitudPalabra longitud;
	char* buffer;
	bool borrado;
	this->stream.seekg(sizeof(bool), std::ios::beg);
	this->space->eliminarTodo();
	this->indice.clear();
	while ((!this->stream.eof())&&(this->stream.peek()!=std::ios::traits_type::eof())){
		offset = this->stream.tellg();
		this->stream.seekg(offset,std::ios::beg);
		this->stream.read(reinterpret_cast<char*>(&borrado), sizeof(bool));
		//std::cout << offset << " borrado " << borrado ; 
		this->stream.read(reinterpret_cast<char*>(&longitud), sizeof(DBFrecuencias::LongitudPalabra));
		if (borrado) {
			this->stream.seekg(longitud, std::ios::cur);
			this->stream.read(reinterpret_cast<char*>(&cantidad),sizeof(DBFrecuencias::CantidadTags));
			//std::cout << " cantidad " << cantidad ;				
			this->space->agregarEspacio( longitud + (cantidad*(sizeof(DBFrecuencias::SElemento))), offset);
			this->stream.seekg(sizeof(DBFrecuencias::SElemento)*cantidad,std::ios::cur);
		} else {
			buffer = new char[longitud+1];
			this->stream.read(buffer,longitud);
			buffer[longitud]='\0';
			id.assign(buffer);
			//std::cout << " palabra " << id ;
			delete[] buffer;
			this->stream.read(reinterpret_cast<char*>(&cantidad),sizeof(DBFrecuencias::CantidadTags));
			//std::cout << " cantidad " << cantidad ;
			this->stream.seekg(sizeof(DBFrecuencias::SElemento)*cantidad,std::ios::cur);
			this->indice.insert(std::pair<DBFrecuencias::Palabra,DBFrecuencias::Offset>(id,offset));
		}
		//std::cout << std::endl;
	}
	this->stream.clear();
}

void CManagerFile::decrementarFrecuencia(DBFrecuencias::Palabra id, DBFrecuencias::TagId tagId, DBFrecuencias::Frecuencia frec) {
	DBFrecuencias::IndiceArchivo::iterator it = this->indice.find(id);
	DBFrecuencias::Offset offset;
	this->managerTag->decrementarNumeroPalabras(tagId,frec);
	if (it != this->indice.end()) {
		offset = it->second;
		this->stream.seekg(offset+sizeof(bool), std::ios::beg);
		//saltea la el string
		DBFrecuencias::LongitudPalabra longitud;
		this->stream.read(reinterpret_cast<char*>(&longitud),sizeof(DBFrecuencias::LongitudPalabra));		
		this->stream.seekg(longitud, std::ios::cur);
		// Estaba en el File
		DBFrecuencias::CantidadTags cantidad;
		this->stream.read(reinterpret_cast<char*>(&cantidad),sizeof(DBFrecuencias::CantidadTags));
		DBFrecuencias::SElemento* elementos = new DBFrecuencias::SElemento[cantidad];
		this->stream.read(reinterpret_cast<char*>(elementos),sizeof(DBFrecuencias::SElemento)*cantidad);
		for (unsigned int i=0; i < cantidad ; i++) {
			if (elementos[i].id==tagId) {
				// Estaba en el File, y estaba el Tag id
				int aux = elementos[i].frecuencia - frec;
				if (!(aux <= 0)) {
					//no llego a cero
					elementos[i].frecuencia = aux;
					this->stream.seekp(offset + this->tamanio + id.size() +(i* sizeof(DBFrecuencias::SElemento)), std::ios::beg);
					this->stream.write(reinterpret_cast<const char*>(&elementos[i]),sizeof(DBFrecuencias::SElemento));
					delete[] elementos;
					return;
				} else {
					//la frecuencia llego a cero, hay que eliminarla de los archivos
					delete[] elementos;
					this->managerTag->eliminarPalabra(tagId, id);
					offset = it->second;
					if (cantidad != 1) {
						//hay mas structs en el registro
						DBFrecuencias::Offset nuevo = this->space->consultarEspacio(id.size()+((cantidad - 1)*(sizeof(DBFrecuencias::SElemento))));
						this->truncarRegistro(offset, nuevo, tagId);
						this->eliminarRegistro(id);
						this->indice.insert(std::pair<DBFrecuencias::Palabra,DBFrecuencias::Offset>(id,nuevo));
						if(this->reorganizar){
							this->reorganizarArchivo();
						}
						return;
					} else {
						//era el unico reg en la estructura
						this->eliminarRegistro(id);
						if(this->reorganizar){
							this->reorganizarArchivo();
						}
						return;
					}
				}
			}//if(elemento.id==tagId){
		}//for
	}//if(it != this->indice.end())
}

void CManagerFile::truncarRegistro(DBFrecuencias::Offset origen,
		DBFrecuencias::Offset destino, DBFrecuencias::TagId tagId) {
	DBFrecuencias::Palabra id;
	DBFrecuencias::SElemento* elemento;
	DBFrecuencias::CantidadTags cantidad;
	DBFrecuencias::LongitudPalabra longitud;
	bool estado;

	this->stream.seekg(origen, std::ios::beg);
	this->stream.read(reinterpret_cast<char*>(&estado), sizeof(bool));
	this->stream.read(reinterpret_cast<char*>(&longitud), sizeof(DBFrecuencias::LongitudPalabra));
	
	char* buffer = new char[longitud+1];
	
	this->stream.read(reinterpret_cast<char*>(buffer),longitud);
	buffer[longitud]='\0';
	id.assign(buffer);
	
	delete[] buffer;
	
	this->stream.read(reinterpret_cast<char*>(&cantidad),sizeof(DBFrecuencias::CantidadTags));
	elemento = new DBFrecuencias::SElemento[cantidad];
	this->stream.read(reinterpret_cast<char*>(elemento),sizeof(DBFrecuencias::SElemento)*cantidad);
	cantidad--;
	this->stream.seekp(destino, std::ios::beg);
	this->stream.write(reinterpret_cast<const char*>(&estado), sizeof(bool));
	this->stream.write(reinterpret_cast<const char*>(&longitud), sizeof(DBFrecuencias::LongitudPalabra));
	this->stream.write(id.c_str(),longitud);
	this->stream.write(reinterpret_cast<const char*>(&cantidad),sizeof(DBFrecuencias::CantidadTags));
	cantidad++;
	//this->stream.write(reinterpret_cast<const char*>(elemento),	sizeof(DBFrecuencias::SElemento)*cantidad);
	for(unsigned int i=0 ; i < cantidad ; i++){
		if(elemento[i].id != tagId){	
			this->stream.write(reinterpret_cast<const char*>(&elemento[i]),sizeof(DBFrecuencias::SElemento));
		}
	}
	
	delete[] elemento;
}

void CManagerFile::eliminarTag(DBFrecuencias::TagId tagId) {
	if (this->managerTag->abrirTag(tagId)) {
		//se pudo abrir el archivo del tag
		while (!this->managerTag->eof()) {
			DBFrecuencias::Palabra id = this->managerTag->leerPalabra();
			DBFrecuencias::IndiceArchivo::iterator it = this->indice.find(id);
			if (it != this->indice.end()) {
				//la palabra esta en el indice
				DBFrecuencias::Offset offset= it->second;
				DBFrecuencias::CantidadTags cantidad;
				DBFrecuencias::LongitudPalabra longitud;
				bool borrado=false;
				this->stream.seekg(offset, std::ios::beg);
				this->stream.read(reinterpret_cast<char*>(&borrado),sizeof(bool));
				if (!borrado) {
					// no esta borrado
					this->stream.read(reinterpret_cast<char*>(&longitud),sizeof(DBFrecuencias::LongitudPalabra));
					this->stream.seekg(longitud,std::ios::cur);
					this->stream.read(reinterpret_cast<char*>(&cantidad),sizeof(DBFrecuencias::CantidadTags));
					if (cantidad != 1) {
						// Escribe el registro en otra posicion, sin la frec del Tag a borrar
						DBFrecuencias::Offset nuevo = this->space->consultarEspacio(longitud + ((cantidad-1)*sizeof(DBFrecuencias::SElemento)));
						this->truncarRegistro(offset, nuevo, tagId);
						this->eliminarRegistro(id);
						this->indice.insert(std::pair<DBFrecuencias::Palabra,DBFrecuencias::Offset>(id,nuevo));
						if(this->reorganizar){
							this->reorganizarArchivo();
						}
					} else {
						//era la unica frecuencia en el registro
						DBFrecuencias::SElemento elemento;
						this->stream.read(reinterpret_cast<char*>(&elemento),sizeof(DBFrecuencias::SElemento));
						if(elemento.id == tagId){
							this->eliminarRegistro(id);
							if(this->reorganizar){
								this->reorganizarArchivo();
							}
						}
					}
				}
			}
		}
		//this->eliminarTag(tagId);
	}
}

void CManagerFile::cargarIndiceRegistros() {
	this->indice.clear();
	std::fstream file(IDX_FRECUENCIA, std::ios::binary | std::ios::in);
	if (file.is_open()) {
		file.seekg(0, std::ios::beg);
		DBFrecuencias::Palabra id;
		DBFrecuencias::Offset offset;
		while ((!file.eof())&&(file.peek()!=std::ios::traits_type::eof())) {
			DBFrecuencias::LongitudPalabra longitud;
			file.read(reinterpret_cast<char*>(&longitud),sizeof(DBFrecuencias::LongitudPalabra));
			char* buffer = new char[longitud+1];
			file.read(reinterpret_cast<char*>(buffer),longitud);
			buffer[longitud] ='\0';
			id.assign(buffer);
			delete[] buffer;
			file.read(reinterpret_cast<char*>(&offset),sizeof(DBFrecuencias::Offset));
			this->indice.insert(std::pair<DBFrecuencias::Palabra,DBFrecuencias::Offset>(id,offset));
		}
	}
	file.close();
}

void CManagerFile::guardarIndiceRegistros() {
	std::fstream file(IDX_FRECUENCIA, std::ios::binary	| std::ios::out | std::ios::trunc );
	if (file.is_open()) {
		file.seekp(0, std::ios::beg);
		DBFrecuencias::Palabra id;
		DBFrecuencias::Offset offset;
		DBFrecuencias::IndiceArchivo::iterator it = this->indice.begin();
		for (; it != this->indice.end() ; it++) {
			id = it->first;
			offset = it->second;
			DBFrecuencias::LongitudPalabra longitud = id.length();
			file.write(reinterpret_cast<const char*>(&longitud),sizeof(DBFrecuencias::LongitudPalabra));
			file.write(reinterpret_cast<const char*>(id.c_str()),longitud);
			file.write(reinterpret_cast<const char*>(&offset),sizeof(DBFrecuencias::Offset));
		}
	}
	file.close();
}

void CManagerFile::reorganizarArchivo() {
	std::fstream file("./frecuencias.dat.1", std::ios::binary | std::ios::out| std::ios::trunc );
	if (!this->stream.is_open()) {
		this->stream.open(DB_FRECUENCIA, std::ios::binary | std::ios::in);
		this->cargarIndiceRegistros();
	}
	file.seekp(0, std::ios::beg);
	DBFrecuencias::IndiceArchivo::iterator it = this->indice.begin();
	DBFrecuencias::Offset offset;
	DBFrecuencias::Palabra palabra;
	for (; it != this->indice.end() ; it++) {
		DBFrecuencias::Offset aux = file.tellp();
		file.seekp(aux);
		palabra = it->first;
		DBFrecuencias::CantidadTags cantidad;
		DBFrecuencias::SElemento* elementos;
		bool borrado=false;
		offset = it->second;
		this->stream.seekg(offset+sizeof(bool), std::ios::beg);
		this->stream.read(reinterpret_cast<char*>(&cantidad),sizeof(DBFrecuencias::CantidadTags));
		elementos = new DBFrecuencias::SElemento[cantidad];
		this->stream.read(reinterpret_cast<char*>(elementos),sizeof(DBFrecuencias::SElemento)*cantidad);
		
		file.write(reinterpret_cast<const char*>(&borrado), sizeof(bool));
		file.write(reinterpret_cast<const char*>(&cantidad),sizeof(DBFrecuencias::CantidadTags));
		file.write(reinterpret_cast<const char*>(elementos),sizeof(DBFrecuencias::SElemento)*cantidad);
		delete[] elementos;
		this->indice.erase(it);
		this->indice.insert(std::pair<DBFrecuencias::Palabra,DBFrecuencias::Offset>(palabra,aux));
	}
	file.close();
	this->stream.close();
	this->space->eliminarTodo();
	unlink(DB_FRECUENCIA);
	rename("./frecuencias.dat.1",DB_FRECUENCIA);
	this->stream.open(DB_FRECUENCIA,std::ios::binary | std::ios::in | std::ios::out);
	this->reorganizar = false;
}

void CManagerFile::setTagManager(CManagerTag* manager) { 
		this->managerTag = manager;
}
